#!/bin/env python3
# -*- coding:utf-8 -*-
"""
    挂载节点文件系统
"""

from __future__ import with_statement

import os
import sys
import errno
from pdb import set_trace as strace
from traceback  import format_exc as dumpstack
import base64,functools
from e4ting import util,log

from fuse  import FUSE, FuseOSError, Operations
from e4ting.cluster.fuse import FUSE as Fuser
from core.rpc.caller     import NodeControl

def error(ret):
    return OSError(ret["errno"], ret["strerror"], ret["filename"])

def hook_local_path(function):
    @functools.wraps(function)
    def wrapper0(self, *args, **kwargs):
        try:
            if args and args[0] . endswith("MAILPATH"):
                raise OSError(2, "", "")
            if args and args[0] == '/':
                log.info((function.__name__, *args, kwargs))
                ret = wrapper0.hook(*args, **kwargs)
            else:
                ret = function(self, *args, **kwargs)
            # log.info((function.__name__, *args, kwargs, ret))
            return ret
        except KeyboardInterrupt as e:
            # 响应 Ctrl + C 中断
            raise e
    wrapper0.hook = getattr(Fuser, function.__name__)
    return wrapper0

class FuseNode(Operations):
    def __init__(self, node=None):
        self.node = NodeControl("")
        # self._client = self.node._client
        # self._client.start()
        # self.preuri  = self.node.preuri
        # log.info(self.node.hello())

    def _call_(self, attr, path, *args):
        names = path.split('/')
        uid, _path = names.pop(1), '/'.join( names ) or '/'
        # preuri = f"e4ting.{uid}.{attr}"
        # log.debug((_path, *args))
        node = NodeControl(uid)
        log.info((attr, _path))
        ret = node.callback("fuse", attr, _path, *args)
        if type(ret) is dict and "errno" in ret:
            log.info((attr, _path, ret))
            raise OSError(ret["errno"], ret["strerror"], ret["filename"])
        return ret

    # @hook_local_path
    def access(self, path, mode):
        if path == '/':
            return
        ret = self._call_("access", path, mode)
        if not ret:
            raise FuseOSError(errno.EACCES)
        # return ret  # 千万不要动这里，否则后果严重

    @hook_local_path
    def chmod(self, path, mode):
        return self._call_("chmod", path, mode)

    @hook_local_path
    def chown(self, path, uid, gid):
        return self._call_("chown", path, uid, gid)

    @hook_local_path
    def getattr(self, path, fh=0):
        ret = self._call_("getattr", path, fh or 0)
        # if "errno" in ret:
        #     raise error(ret)
        return ret

    # @hook_local_path
    def readdir(self, path, fh):
        if path == '/':
            dirents = [ _ for _ in self.node.list_fuse() ]
        else:
            dirents = self._call_("readdir", path, fh)
        if type(dirents) is dict and dirents["errno"]:
            raise error(dirents)
        for r in dirents:
            yield r

    @hook_local_path
    def readlink(self, path):
        return self._call_("readlink", path)

    @hook_local_path
    def mknod(self, path, mode, dev):
        return self._call_("mknod", path, mode, dev)

    @hook_local_path
    def rmdir(self, path):
        return self._call_("rmdir", path)

    @hook_local_path
    def mkdir(self, path, mode):
        return self._call_("mkdir", path, mode)

    @hook_local_path
    def statfs(self, path):
        try:
            return self._call_("statfs", path)
        except:
            log.info(dumpstack())
            raise OSError(2, "dev [{}] is offline".format(self.node.nodeid))

    @hook_local_path
    def unlink(self, path):
        ret = self._call_("unlink", path)
        return

    @hook_local_path
    def symlink(self, name, target):
        return self._call_("symlink", name, target)

    @hook_local_path
    def rename(self, old, new):
        return self._call_("rename", old, new)

    @hook_local_path
    def link(self, target, name):

        return self._call_("link", target, name)

    @hook_local_path
    def utimens(self, path, times=None):
        return self._call_("utimens", path, times)

    # File methods
    # ============

    @hook_local_path
    def open(self, path, flags):
        return self._call_("open", path, flags)

    @hook_local_path
    def create(self, path, mode, fi=0):
        return self._call_("create", path, mode, fi)

    @hook_local_path
    def read(self, path, length, offset, fh):
        ret = self._call_("read", path, length, offset, fh)
        if type(ret) is dict and "errno" in ret:
            log.info(ret)
            raise OSError(ret["errno"], ret["strerror"], ret["filename"])
        ret = base64.b64decode(ret)
        # log.info((len(ret), ret))
        return ret

    @hook_local_path
    def write(self, path, buf, offset, fh):
        buf = base64.b64encode(buf).decode()
        # return self._call_("fuse", "write", path, base64.b64encode(buf), offset, fh)
        return self._call_("write", path, buf, offset, fh)

    @hook_local_path
    def truncate(self, path, length, fh=0):
        return self._call_("truncate", path, length, fh)

    @hook_local_path
    def flush(self, path, fh):
        ret = self._call_("flush", path, fh)
        if ret == True:
            return
        # if ret and "errno" in ret:
        #     raise OSError(ret["errno"], ret["strerror"], ret["filename"])
        return

    @hook_local_path
    def release(self, path, fh):
        return self._call_("release", path, fh)

    @hook_local_path
    def fsync(self, path, fdatasync, fh):
        return self._call_("fsync", path, fdatasync, fh)

# @util.processed
@util.redef_return(ret=False)
def mount(path):
    point = str(path)
    # 挂载之前先卸载，避免文件系统存在Transport endpoint is not connected状态，不重新挂载
    util.system(f"umount {point}".format(point=point))
    if not os.path.exists(point):
        try:
            os.mkdir(point)
        except Exception as e:
            pass
    log.info("挂载文件系统 {point}".format(point=point))
    FUSE(FuseNode(), point, foreground=True)
    return True

if __name__ == '__main__':
    mount(sys.argv[1])
